# coding=utf-8

import re
import os.path
import time

from subprocess import Popen, PIPE
from collections import namedtuple

# 全局变量

# perforce 服务器代码库路径
PERFORCE_DEPOT = ''

# perforce 和 git 命令执行的工作目录（本地代码库路径）
WORKSPACE_DIR = ''

# perforce 当前工作区
P4_CLIENT = None

def SetPerforceClient(val):
    global P4_CLIENT
    P4_CLIENT = val

def SetPerforceDepot(depot):
    global PERFORCE_DEPOT
    PERFORCE_DEPOT = depot

def SetWorkspaceDir(dir):
    global WORKSPACE_DIR
    WORKSPACE_DIR = dir

def ExecCmd(args, universal_newlines = None):
    # print(args)
    # print(WORKSPACE_DIR)
    if universal_newlines is None:
        universal_newlines = False

    assert isinstance(args, str) or isinstance(args, list)

    if isinstance(args, str):
        args = re.split(r"\s+", args)

    try:
        assert P4_CLIENT is not None
    except KeyError:
        print ("You must set `P4CLIENT` before using this script.")
        return None, None

    SCRIPT_PATH = os.path.dirname(os.path.realpath(__file__))
    LOG_FILE = os.path.join(SCRIPT_PATH, "log.txt")
    with open(LOG_FILE, "a") as f:
        f.write(' '.join(args))
        f.write('\n')

    perforce = Popen(args, stdout = PIPE, stderr = PIPE, cwd = WORKSPACE_DIR, universal_newlines=universal_newlines)

    def InnerDecode(x):
        assert universal_newlines == False
        if x:
            x = x.decode(errors="ignore")
        return x

    out, err = perforce.communicate()
    if universal_newlines == False:
        out, err = [x for x in map(InnerDecode, (out, err))]

    if out or err:
        with open(LOG_FILE, "a") as f:
            if out:
                f.write("Output: " + out + "\n")
            if err:
                f.write("Error: " + err + "\n")

    if perforce.returncode != 0:
        raise RuntimeError

    return out, err

# 返回 git 的当前分支
def GetGitCurrentBranch():
    args = "git branch"
    out, err = ExecCmd(args, True)
    m = re.search(r"^\* (.+)$", out, re.M)
    if m:
        result = m.group(1)
    return result

# 检查 perforce 当前是否有处于 pending 状态，且有正在编辑文件的 change list 。如果有，提示并退出程序。
def HasPerforcePendingChanges():
    args = ['p4', '-c', P4_CLIENT, 'changes', '-u', 'chanchen', '-c', P4_CLIENT]
    out, err = ExecCmd(args, True)

    PENDING_PATTERN = r"^Change (\d+) .+ \*pending\*"
    pendings = []
    for x in out.splitlines():
        m = re.match(PENDING_PATTERN, x)
        if m:
            pendings.append(m.group(1))

    PENDING_FILE_PATTERN = r"^\.\.\. .+$"
    hasPendingFiles = False
    for cl in pendings:
        args = ['p4', '-c', P4_CLIENT,  'describe', cl]
        out, err = ExecCmd(args)
        pending_files = []
        for x in out.splitlines():
            m = re.match(PENDING_FILE_PATTERN, x)
            if m:
                pending_files.append(m.group(0))
        if len(pending_files) > 0:
            hasPendingFiles = True
            print("Please revert the CL: %s" % cl)
            for f in pending_files:
                print(f)
            print()

    return hasPendingFiles

# 获取 perforce 所有大于指定 change 的 change 列表。
def GetPerforceChangesAfterChange(Change, Max = 100):
    args = ['p4', '-c', P4_CLIENT, 'changes', '-m', str(Max), *PERFORCE_DEPOT]
    out, err = ExecCmd(args)

    changes = []
    for x in sorted(out.splitlines(), reverse = True):
        cl = x.split()[1]
        if cl == Change:
            cl_found = True
            break
        changes.append(cl)
    else:
        cl_found = False
    if not cl_found:
        print("#CL_NUM %s is not found!" % Change)
        exit(1)

    changes.reverse()
    return changes

def GetLocalPathOfDeptFile(depotPath):
    out, err = ExecCmd(['p4', '-c', P4_CLIENT, 'where', depotPath])

    localFileName = out[out.find(":")-1:].rstrip()
    return os.path.relpath(localFileName, WORKSPACE_DIR)

# item 为更新的文件，包含“文件路径”及“操作模式”。
item = namedtuple('item', ['filepath', 'localFilePath', 'mode'])

# 得到 perforce 中指定的 change 中的文件列表（包含“文件路径”，及“操作模式”），并返回 change 描述。
def GetPerforceFilesByChange(Change):
    out, err = ExecCmd(['p4', '-c', P4_CLIENT, 'describe', '-s', Change])
    PATTERN = r"\.\.\. (.+)#(?:[0-9]+) (add|move/add|edit|delete|move/delete|branch|integrate)"

    items = []
    for ln in out.splitlines():
        if ln.startswith('... '):
            print(ln)
            m = re.search(PATTERN, ln)
            assert m
            items.append(item(m.group(1), GetLocalPathOfDeptFile(m.group(1)), m.group(2)))

    return items, out

def WaitForFileReady(filepath):
    if not os.path.isfile(filepath):
        secs = 0
        while True:
            time.sleep(1)
            if not os.path.isfile(filepath):
                break
            secs += 1
            print("Wait %d seconds ...\n" % secs)
            if secs >= 60: # 最多等待 60 秒
                break

def UpdateGitFile(item, change):
    out, err = ExecCmd(['p4', '-c', P4_CLIENT, 'sync', '-f', item.filepath + '@' + change])
    print(out)

    if item.mode in ('add', 'move/add', 'edit', 'branch', 'integrate'):

        # 等待文件准备好。
        # p4 命令返回时，因为文件处理延迟，可能此时文件还未准备好， git add 命令会失败。
        WaitForFileReady(os.path.join(WORKSPACE_DIR, *item.filepath.split('/')))

        out, err = ExecCmd(['git', 'add', '-f', item.localFilePath])
    else:
        assert item.mode in ('delete', 'move/delete')
        out, err = ExecCmd(['git', 'rm', item.localFilePath])
    # print(out)

def GetCommitMessageFile(desc):

    SCRIPT_PATH = os.path.dirname(os.path.realpath(__file__))

    message_path = os.path.join(SCRIPT_PATH, "messages")
    if not os.path.isdir(message_path):
        os.mkdir(message_path)

    def GetUnusedCommitMessageFileName(n):
        filename = os.path.join(message_path, "{:05}.txt".format(n))
        while os.path.isfile(filename):
            n += 1
            filename = os.path.join(message_path, "{:05}.txt".format(n))
        return filename, n

    commit_file = os.path.join(SCRIPT_PATH, "commit.txt")
    try:
        with open(commit_file) as f:
            MaxNum = int(f.readline().strip())
    except FileNotFoundError:
        MaxNum = 1

    filename, MaxNum = GetUnusedCommitMessageFileName(MaxNum)
    with open(filename, "w") as f:
        f.write(desc)

    MaxNum += 1
    with open(commit_file, "w") as f:
        f.write(str(MaxNum))

    return filename

def TransferChangeFromP4ToGit(change):
    items, desc = GetPerforceFilesByChange(change)

    modes = [
        "delete",
        "move/delete",
        "move/add",
        "edit",
        "add",
        "branch",
        "integrate",
    ]

    items.sort(key = lambda x: modes.index(x.mode))

    for item in items:
        UpdateGitFile(item, change)

    ExecCmd(['git', 'commit', '--no-verify', '-F', GetCommitMessageFile(desc)])

if __name__ == "__main__":
    pass
